/**
 * @fileOverview BaseObject 基类，实现事件、继承、序列化等基础功能
 * @author goodzsq@gmail.com
 */
var Events = gg.module('Events');
var Resource = gg.module('Resource');
var EventDataType = gg.module('datatype.res.Event');

var objContainer = gg.objectContainer;

var BaseObject = function BaseObject(config, parent) {
    var id, cid = _.uniqueId('c');
    config = config || {};
    config.id && (id = cid = config.id);

    if (objContainer[cid]) {
        gg.error('create object fail, id already exist:' + cid);
    }
    this.id = id;
    this.cid = cid;
    this.parent = parent;
    this._configure(config); /*默认值的属性参数*/
    objContainer[this.cid] = this;
    this.beforeInit();
    this.init();
    this.afterInit();
};

_.extend(BaseObject.prototype, Events, {
    beforeInit: function() {

    },
    afterInit: function() {

    },
    /**
     * 初始化
     */
    init: function() {
        this._attributes = {}; //未设置setter的属性存储区
        //将属性排序
        var attrs = _.keys(this.config);
        var self = this;
        attrs.sort(function(a, b) {
            var order1 = 0, order2 = 0;
            if (self.constructor.metadata[a]) {
                order1 = self.constructor.metadata[a].order || 0;
            }
            if (self.constructor.metadata[b]) {
                order2 = self.constructor.metadata[b].order || 0;
            }
            return order1 - order2;
        });
        for (var i = 0; i < attrs.length; i++) {
            var key = attrs[i];
            var value = this.config[key];
            //如果属性默认值未定义则不进行设置
            if (value !== void 0) {
                this.set(key, value);
            } else {
                this.defaultSetter(key, void 0);
            }
        }
        this.inited = true;
    },
    /**
     * 设置/获取set方法是否能够触发change事件
     */
    _allowChange: false,
    allowChange: function() {
        if (arguments.length === 0) {
            return this._allowChange;
        } else {
            this._allowChange = !!arguments[0];
        }
    },
    /**
     * 设置/获取父亲元素
     */
    _parent: null,
    parent: function() {
        if (arguments.length === 0) {
            return this._parent;
        } else {
            this._parent = arguments[0];
        }
    },
    /**
     * 检查对象是否具有某种模块特征
     * 当类函数由gg.createClass构造的时，这个类就具有参数指定的子模块的特征
     * @param {String} trait
     */
    hasTrait: function(trait) {
        return this.constructor.hasTrait(trait);
    },
    /**
     * 构造配置参数，将构造参数与属性的默认值合并
     * @param {Object} config
     */
    _configure: function(config) {
        var attrs = config || {};
        var metadata = this.constructor.metadata;
        //根据元数据获取默认值
        var defaults = {};
        _.each(metadata, function(v, k) {
            defaults[k] = _.result(v, 'defaultValue');
        });
        this.config = _.defaults({}, attrs, defaults);
        delete this.config.classType;
        delete this.config.id;
    },
    /**
     * 导出对象数据
     */
    toJSON: function() {
        var result = {};
        _.each(this._attributes, function(v, k) {
            var metadata = this.constructor.metadata[k];
            if (!metadata) {
                result[k] = v;
            } else {
                if (metadata && metadata.getter) {
                    v = metadata.getter.call(this);
                }
                //只存储不等于默认值的
                if (_.result(metadata, 'defaultValue') !== v) {
                    if (v instanceof BaseObject) {
                        result[k] = v.toJSON();
                    } else {
                        result[k] = v;
                    }
                }
            }
        }, this);
        result.classType = this.constructor.classType;
        if (this.id) {
            result.id = this.id;
        }
        return result;
    },
    /**
     * 克隆对象
     */
    clone: function() {
        var attrs = this.toJSON();
        delete attrs.id;
        return new this.constructor(attrs);
    },
    /**
     * 销毁对象
     */
    destroy: function() {
        this.trigger('destroy', this);
        this.stopListening();
        this.off();
        var keys = this.keys();
        var obj;
        for (var i = 0; i < keys.length; i++) {
            obj = this.get(keys[i]);
            if (obj instanceof BaseObject) {
                this.set(keys[i], undefined);
            }
        }
        if (this.parent && this.parentAttr) {
            this.parent.clearAttr(this.parentAttr);
        }
        delete objContainer[this.cid];
        console.log('destory', this.cid, this.constructor.classType);
    },
    /**
     * 清除属性值
     * @param {String} key 属性名称
     */
    clearAttr: function(key) {
        var metadata = this.constructor.metadata[key];
        if (metadata.setter) {
            metadata.setter.call(this, undefined);
        } else {
            this.defaultSetter(key, undefined);
        }
        delete this._attributes[key];
    },
    /**
     * 默认属性设置器
     * @param {String} key 属性名称
     * @param {Mixed} value 属性值
     */
    defaultSetter: function(key, value) {
        this._attributes[key] = value;
        return true;
    },
    /**
     * 默认属性获取器
     * @param {String} key 属性名称
     */
    defaultGetter: function(key) {
        return this._attributes[key];
    },
    /**
     * 获取对象属性值
     * @param {String} key 属性名称
     * @return {Mixed}
     */
    get: function(key) {
        var metadata = this.constructor.metadata[key];
        if (metadata) {
            if (metadata && metadata.getter) {
                return metadata.getter.call(this, key);
            } else {
                return this.defaultGetter(key);
            }
        } else {
            return this.defaultGetter(key);
        }
    },
    /**
     * 设置对象属性值
     * @param {String} key 属性名称
     * @param {Mixed} value 属性值
     */
    set: function(key, value) {
        var oldValue = this.get(key);
        if (oldValue === value) {
            return false;
        }
        var newValue = value;
        var result;
        var metadata = this.constructor.metadata[key];
        if (metadata) {
            var DataType = this.dataType(key);
            //安全类型转换
            if (typeof DataType === 'function') {
                if (value) {
                    if (gg.type(value) === 'object' && !value.classType) {
                        value.classType = DataType.classType;
                    }
                    newValue = gg.object(value, this);
                    newValue.parentAttr = key;
                }
            } else if (DataType && typeof DataType.parse === 'function') {
                try {
                    newValue = DataType.parse(value);
                } catch (e) {
                    gg.error(metadata.type + ' parse fail:' + value);
                }
            }
            if (metadata.setter) {
                //占位，方便获取属性列表集合 keys
                this._attributes[key] = void 0;
                result = metadata.setter.call(this, newValue, oldValue);
            } else {
                result = this.defaultSetter(key, newValue);
            }
            if (oldValue instanceof BaseObject) {
                //将原来的对象销毁掉
                oldValue.destroy();
            }
        } else {
            result = this.defaultSetter(key, newValue);
        }
        if (result !== false && this.allowChange()) {
            this.trigger('change:' + key, this, newValue, oldValue);
            this.trigger('change', this, key, newValue, oldValue);
        }
        return result;
    },
    /**
     * 获取属性的数据类型对象
     * @param {String} key 属性名称
     * @return {Object}
     */
    dataType: function(key) {
        var type, DataType, metadata = this.constructor.metadata[key];
        if (!metadata) {
            return null;
        } else {
            type = metadata.type || 'datatype.String';
        }
        var DataType = gg.module(type);
        if (!DataType) {
            gg.error('datatype not exist:' + type);
        }
        return DataType;
    },
    /**
     * 对属性进行运算操作并更新属性
     * @example obj.applyAttr('position', 'add', {x:1,y:2})
     */
    applyAttr: function(key, operator /* ... */) {
        var newValue = this.calcAttr.apply(this, arguments);
        return this.set(key, newValue);
    },
    /**
     * 对属性进行运算操作
     * @param {String} key 属性名称
     * @param {String | Function} operator 操作方法
     * @param {...} 额外的操作参数
     */
    calcAttr: function(key, operator /*, ... */) {
        if (typeof operator === 'string') {
            operator = this.dataType(key)[operator];
        }
        if (typeof operator === 'function') {
            var args = Array.prototype.slice.call(arguments, 2);
            args.unshift(this.get(key));
            return operator.apply(this, args);
        } else {
            gg.error('datatype operator error');
        }
    },
    /**
     * 重置属性值 若无参数则重置所有属性
     * @param {...} ... 需要重置的属性
     */
    reset: function(/*...*/) {
        if (arguments.length === 0) {
            var keys = this.keys();
            for (var i = 0; i < keys.length; i++) {
                this.reset(keys[i]);
            }
        } else {
            for (var i = 0; i < arguments.length; i++) {
                var key = arguments[i];
                delete this._attributes[key];
                var metadata = this.constructor.metadata[key];
                if (metadata && metadata.setter) {
                    metadata.setter.call(this, metadata.defaultValue);
                }
            }
        }
    },
    /**
     * 列出属性值
     */
    keys: function() {
        return _.keys(this._attributes);
    },
    /**
     * 触发属性事件处理器
     * @param {String} key 事件属性名称
     */
    triggerEvent: function(key) {
        var metadata = this.constructor.metadata[key];
        if (metadata.type === 'res.Event') {
            var resUri = this.get(key);
            var event = EventDataType.load(Resource.get(resUri));
            if (typeof event === 'function') {
                return event.apply(this, arguments);
            }
        }
        return false;
    },
    /**
     * 执行数据绑定
     * @param {String} key 属性
     * @param {Array} options [function, [id, attrName], [id, attrName] ...]
     */
    execBind: function(key, options) {
        var fun = options[0];
        var args = [];
        if (options.length < 2) {
            return false;
        }
        for (var i = 1; i < options.length; i++) {
            var v, obj = gg.object(options[i][0]);
            if (obj) {
                args.push(obj.get(options[i][1]));
            } else {
                args.push(null);
            }
        }
        if (typeof fun === 'function') {
            return this.set(key, fun.apply(this, args));
        } else {
            return this.set(key, args[0]);
        }
    }
});

/**
 * 继承方法
 * 代码来自于backbonejs.org
 * @param {Object} protoProps
 * @param {Object} staticProps
 * @returns {Construct}
 */
BaseObject.extend = function(protoProps, staticProps) {
    var parent = this;
    var child;

    // The constructor function for the new subclass is either defined by you
    // (the "constructor" property in your `extend` definition), or defaulted
    // by us to simply call the parent's constructor.
    if (protoProps && _.has(protoProps, 'constructor')) {
        child = protoProps.constructor;
    } else {
        child = function() {
            return parent.apply(this, arguments);
        };
    }

    // Add static properties to the constructor function, if supplied.
    _.extend(child, parent, staticProps);

    // Set the prototype chain to inherit from `parent`, without calling
    // `parent`'s constructor function.
    var Surrogate = function() {
        this.constructor = child;
    };
    Surrogate.prototype = parent.prototype;
    child.prototype = new Surrogate;

    // Add prototype properties (instance properties) to the subclass,
    // if supplied.
    if (protoProps) {
        _.extend(child.prototype, protoProps);
    }

    // Set a convenience property in case the parent's prototype is needed
    // later.
    child.__super__ = parent.prototype;

    return child;
};

/**
 * 生成类实例对象的工厂方法
 * 跟据对象的父子关系，子对象可以获取父亲对象的信息，比如视图对象的模型可以分享给子对象使用
 * @param {Object} attributes 构建对象的配置信息
 * @param {BaseObject} parent 设置对象的父亲对象
 * @returns {unresolved}
 */
BaseObject.create = function(attributes, parent) {
    var obj = new this(attributes, parent);
    return obj;
};

/**
 * 获取父类构造函数
 * @returns {BaseObject}
 */
BaseObject.superClass = function() {
    if (this.__super__) {
        return this.__super__.constructor;
    }
};

//获取类继承信息
function getClassInfo(fn) {
    var result = [];
    if (typeof fn === 'function') {
        if (fn && fn.classType) {
            result.push(fn.classType);
        }
        if (typeof fn.superClass === 'function') {
            result = result.concat(getClassInfo(fn.superClass()));
        }
    }
    return result;
}

/**
 * 获取类继承信息
 * @returns {Array}
 */
BaseObject.inheritInfo = function() {
    return getClassInfo(this);
};

/**
 * 属性信息描述元数据
 */
BaseObject.metadata = {
    id: {
        label: 'ID',
        type: 'datatype.String',
        order: -Infinity,
        setter: function(v) {
            if (this.id === v) {
                return true;
            }
            if (!v || v in objContainer) {
                return false;
            }
            delete objContainer[this.cid];
            this.id = this.cid = v;
            objContainer[v] = this;
            return true;
        },
        getter: function() {
            return this.id;
        }
    },
    bind: {
        label: '数据绑定',
        type: 'datatype.Object',
        order: Infinity,
        defaultValue: {},
        setter: function(obj) {
            this.stopListening();
            _.each(obj, function(v, k) {
                this.execBind(k, v);
                for (var i = 1; i < v.length; i++) {
                    this.listenTo(gg.object(v[i][0]), 'change:' + v[i][1], function() {
                        this.execBind(k, v);
                    });
                }
            }, this);
            this.defaultSetter('bind', obj);
        }
    }
};

/**
 * 动态创建类时候存储的扩充模块信息
 */
BaseObject.traits = {};

/**
 * 判断类是否包含trait模块
 * @param {String} trait
 * @returns {Boolean}
 */
BaseObject.hasTrait = function(trait) {
    return trait in this.traits;
};

module.exports = BaseObject;


/** @fileend */

//unittest
UnitTest.add('BaseObject类模块测试',
        function() {
            var obj = BaseObject.create();
            this.test(obj instanceof BaseObject, '无参数的构造函数');
            this.test(gg.object(obj.cid) === obj, '能否通过cid从gg.object中取出');
            this.test(obj.id == null, '对象id为空');
            obj.destroy();
            obj = BaseObject.create({id: 'test', a: 1, b: {x: 100, y: 200}});
            this.test(obj.id === obj.cid, '有id时 id与cid相等');
            this.test(gg.object('test') === obj, '通过id取出对象');
            this.test(obj.get('a') === 1, '属性取值');
            this.testObject(obj.get('b'), {x: 100, y: 200}, '属性取值');
            this.testArray(obj.keys(), ['a', 'b', 'bind'], '属性列表');
            obj.set('a', 'abc');
            this.test(obj.get('a') === 'abc', '给存在的属性赋值成功');
            this.test(obj.get('c') == null, '获取不存在的属性');
            obj.set('c', 'ccc');
            this.test(obj.get('c') === 'ccc', '给不存在的属性赋值成功');
            this.testObject(obj.toJSON(), {a: 'abc', b: {x: 100, y: 200}, c: 'ccc'});
            this.testSet(obj.keys(), ['a', 'b', 'c'], '属性列表');
            var obj1 = obj.clone();
            this.test(obj.constructor === obj1.constructor, '克隆对象有相同的构造函数');
            this.test(obj1.id == null, '克隆对象没有id')
            this.test(obj.cid !== obj1.cid, '可怜对象的cid不相同');
            this.testSet(obj1.keys(), obj.keys(), '属性是相同的');
            var json1 = obj.toJSON(), json2 = obj1.toJSON();
            this.test(json1.id === 'test', '有id的对象导出json应该具有id');
            this.test(json2.id == null, '无id的对象导出json无id');
            delete json1.id;
            this.testObject(json1, json2, '去掉id的两个json对象应该是相同的');
            var cid = obj1.cid;
            obj1.set('id', 'test1');
            this.test(obj1.id === obj1.cid && obj1.id === 'test1', 'id已经改变');
            this.test(gg.object(cid) == null && gg.object('test1') != null, 'gg.object对象取值1');
            obj1.set('id', 'test2');
            this.test(gg.object('test1') == null && gg.object('test2') != null, 'gg.object对象取值2');
            obj.destroy();
            obj1.destroy();
        }
);

UnitTest.add('BaseObject的事件测试',
        function() {
            var obj = gg.object({a: 'aaa', b: 2});
            var callback = this.testCallback('测试改变属性值事件的发生', function(arg1, arg2, arg3) {
                if (arg1 === obj && arg2 === 'abc' && arg3 === 'aaa')
                    return true;
                else
                    return false;
            });
            obj.on('change:a', callback);
            obj.set('a', 'abc');
            obj.off('change:a', callback);

            obj.destroy();
        }
);

UnitTest.add('BaseObject的数据绑定',
        function() {
            var obj1 = gg.object({
                id: 'test',
                a: 'aaa', b: 'bbb', c: 'ccc'
            });
            var obj2 = gg.object({
                classType: 'BaseObject',
                bind: {
                    'abc': [function() {
                            var sum = '';
                            for (var i = 0; i < arguments.length; i++) {
                                sum += arguments[i];
                            }
                            return sum;
                        }, ['test', 'a'], ['test', 'b'], ['test', 'c']]
                }
            });
            this.test(obj2.get('abc') === 'aaabbbccc');
            obj1.set('a', 'a');
            this.test(obj2.get('abc') === 'abbbccc');
            obj1.set('b', 'b');
            this.test(obj2.get('abc') === 'abccc');
            obj1.set('c', 'c');
            this.test(obj2.get('abc') === 'abc');
            obj1.destroy();
            obj2.destroy();
        }
);
